package MicroSpring.beanPostProcessor.impl;

import MicroSpring.annotation.Lazy;
import MicroSpring.beanFactory.impl.BeanDefinition;
import MicroSpring.beanFactory.impl.DefaultListableBeanFactory;
import MicroSpring.beanPostProcessor.DestructionAwareBeanPostProcessor;
import MicroSpring.beanPostProcessor.InstantiationAwareBeanPostProcessor;
import MicroSpring.beanPostProcessor.metadata.InjectionMetadata;
import MicroSpring.common.BeanUtils;
import MicroSpring.exception.BeansException;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashSet;

/*
    @Resource @PostConstruct @PreDestroy
    暂时先实现InstantiationAwareBeanPostProcessor
 */
public class CommonAnnotationBeanPostProcessor implements InstantiationAwareBeanPostProcessor, DestructionAwareBeanPostProcessor {

    private final DefaultListableBeanFactory beanFactory;

    public CommonAnnotationBeanPostProcessor(DefaultListableBeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }



    /*
        依赖注入阶段: 处理@Resource
     */
    @Override
    public void postProcessProperties(Object bean, String beanName) {
        InjectionMetadata metadata = findResourceMetadata(beanName,bean.getClass());
        try {
            metadata.inject(bean,beanName);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * * 查找哪些属性加了@Resource注解,
     *   将需要注入的filed,类型等信息添加到InjectionMetadata里面去.最终由metadata的inject方法来实施注入
     * @param beanName
     * @param clazz
     * @return
     */
    private InjectionMetadata findResourceMetadata(String beanName,Class<?> clazz) {
        InjectionMetadata metadata=new InjectionMetadata(beanFactory);
        metadata.setTargetClass(clazz);
        Field[] declaredFields = clazz.getDeclaredFields();
        Collection<InjectionMetadata.InjectedElement> injectedElements=new HashSet<>();
        for (Field field : declaredFields) {
            field.setAccessible(true);
            InjectionMetadata.InjectedElement  injectedElement=null;
            Resource resource = field.getAnnotation(Resource.class);
            if (resource!=null){
                beanFactory.addBeansInjectBeans(beanName,field);
                if (field.isAnnotationPresent(Lazy.class)){
                    if (BeanUtils.checkLazyBean(field.getType())){
                        beanFactory.addLazyInjectBeans(beanName,field);
                        continue;
                    }
                }
                injectedElement=new InjectionMetadata.InjectedElement();
                injectedElement.setField(field);
                injectedElement.setInjectClazz(field.getType());
            }

            if (injectedElement!=null)
                injectedElements.add(injectedElement);
        }

        metadata.setInjectedElements(injectedElements);
        return metadata;
    }


    /**
     * *初始化之前: 扫描@PostConstruct 注解的方法并且执行
     * @param bean
     * @param beanName
     * @return
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        invokePostConstructMethods(bean);
        return bean;
    }

    private void invokePostConstructMethods(Object bean)  {
        Class<?> clazz = bean.getClass();
        for (Method m : clazz.getDeclaredMethods()) {
            if (m.isAnnotationPresent(PostConstruct.class)){
                if (m.getParameters().length>0){
                    throw new BeansException("@PostConstruct方法不能带参数");
                }
                try {
                    m.invoke(bean);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e.getMessage());
                } catch (InvocationTargetException e) {
                    throw new RuntimeException(e.getMessage());
                }
            }
        }
    }


    /*
            todo 销毁之前: @PreDestroy 注解
         */
    @Override
    public void postProcessBeforeDestruction(Object o, String s) {
        Class<?> clazz = o.getClass();
        for (Method m : clazz.getDeclaredMethods()) {
            if (m.isAnnotationPresent(PreDestroy.class)){
                String beanName = beanFactory.getSingletonBeanNameOfType(clazz);
                BeanDefinition beanDefinition = beanFactory.getBeanDefinitionMap().get(beanName);
                beanDefinition.setPredestroy(true);
                return;
            }
        }
    }
}
